Skip to content

🧱 refactor: storage_session_id rename + kind discriminator (3.1.80-dev.0)#148

Merged
danny-avila merged 2 commits intodevfrom
storage-session-rename
May 7, 2026
Merged

🧱 refactor: storage_session_id rename + kind discriminator (3.1.80-dev.0)#148
danny-avila merged 2 commits intodevfrom
storage-session-rename

Conversation

@danny-avila
Copy link
Copy Markdown
Owner

@danny-avila danny-avila commented May 6, 2026

Summary

Type-system shape for the lockstep cutover of LibreChat ↔ codeapi sandbox file identity. Two distinct concepts had been sharing the field name session_id; this PR also adds the kind discriminator so codeapi's sessionKey derives from explicit resource type rather than emergent legacy behavior.

Companions: codeapi #1455, LibreChat #12960. Pre-release lockstep, no legacy aliases.

Bumps to 3.1.80-dev.0.

Final shape

export const CODE_ENV_KINDS = ['skill', 'agent', 'user'] as const;
export type CodeEnvKind = (typeof CODE_ENV_KINDS)[number];

export type CodeEnvFile =
  | (Base & { kind: 'skill'; version: number })
  | (Base & { kind: 'agent' })
  | (Base & { kind: 'user' });

type Base = {
  /** sessionKey-meaningful for `'skill'` / `'agent'`; informational
   *  only for `'user'` (codeapi resolves user from auth context). */
  id: string;
  name: string;
  /** Long-lived storage bucket, distinct from top-level execution session. */
  storage_session_id: string;
};

What's in this PR

  1. Per-file session_idstorage_session_id rename. Top-level session_id (transient sandbox-run id on CodeSessionContext, ExecuteResult, ProgrammaticExecutionResponse, etc.) keeps its name — only the misnamed side moves. Hard rename, no aliases.
  2. kind discriminator added to CodeEnvFile (required) and FileRef (optional, since /files fallback may not carry it). Drives codeapi's per-resource sessionKey: <tenant>:<kind>:<id>[:v:<version>] for shared kinds, <tenant>:user:<userId> for user-private.
  3. Discriminated union on CodeEnvFile: version: number is statically required for kind: 'skill' and statically forbidden for 'agent' / 'user'. Constraint holds at compile time on every consumer, not just at codeapi's runtime validator.
  4. CODE_ENV_KINDS const-tuple — adding a new kind to the tuple updates both the runtime list and the TS union at once, so a partial update can't silently widen one side.
  5. Drops entity_id from CodeEnvFile, FileRef, and ToolNode propagation. Drops 'system' from the kind union (no emitter ever existed).
  6. toInjectedFileRef helper in ToolNode: collapses the two near-identical CodeEnvFile constructors (the runTool injection path and the event-driven getCodeSessionContext path) onto one call site that does the discriminated-union narrowing in one place. Defaults missing-kind to 'user'; a skill ref missing version falls back to 'user' so an upstream contract bug surfaces as a degraded sessionKey rather than a runtime crash.
  7. JSDoc on id calls out the per-kind asymmetry; on version documents the 'skill'-only requirement.

Sites updated

  • src/types/tools.tsCodeEnvKind, CODE_ENV_KINDS, discriminated CodeEnvFile, JSDoc on FileRef.
  • src/tools/ToolNode.tstoInjectedFileRef helper at runTool injection (_injected_files) and getCodeSessionContext. updateCodeSession and storeCodeSessionFromResults pass new fields through via spread.
  • CodeExecutor, BashExecutor, ProgrammaticToolCalling, BashProgrammaticToolCalling, LocalExecutionTools, LocalProgrammaticToolCalling — per-file refs use storage_session_id; top-level session_id extraction unchanged.
  • Tests: ToolNode.session.test.ts repurposed entity_id-forwarding cases as kind + version forwarding cases (the actual Phase C contract).

Test plan

  • npx tsc --noEmit clean
  • npx jest src/tools src/types — 787 / 787 pass (incl. 18 session-management tests with new per-file shape)
  • Manual: bump LibreChat's @librechat/agents dep to 3.1.80-dev.0 after this lands and run a multi-turn execute against codeapi to confirm.

Why no legacy aliases

The deployment matrix is controlled — codeapi service + LC's pinned @librechat/agents version + LC itself all ship together. A synchronized cutover is fine; alias plumbing for a deploy ordering that doesn't exist is just code to maintain and clean up.

Why keep top-level session_id

Renaming both sides was symmetric but unnecessary. The bug was that per-file session_id was the wrong name (it really meant "storage"). Top-level session_id is the right name (it really means "session"). Disambiguating only the misnamed side gives ~half the churn at every call site, and a future engine that uses one session concept lands on session_id directly with no rename to undo.

@danny-avila danny-avila force-pushed the storage-session-rename branch from a16adfc to 1f1faed Compare May 6, 2026 03:23
@danny-avila
Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila danny-avila force-pushed the storage-session-rename branch 2 times, most recently from fcbd93d to 817d5d3 Compare May 7, 2026 00:14
@danny-avila danny-avila changed the title 🪡 refactor: disambiguate storage vs execution session_id 🧱 refactor: storage_session_id rename + kind discriminator (3.1.79-dev.0) May 7, 2026
…v.0)

Combines Phase B (per-file session_id rename) and Phase C (kind
discriminator for designed cross-user-within-tenant caching) in one
cutover. Pre-release simplifies coordination — no wire-compat aliases.

Type changes:
- `CodeEnvFile`: per-file `session_id` → `storage_session_id`. Adds
  `kind: 'skill' | 'agent' | 'user' | 'system'` (required) and
  `version?: number` (used for `kind: 'skill'`). New `CodeEnvKind`
  type alias exported.
- `FileRef`: same `storage_session_id` rename. `kind?` and `version?`
  optional (legacy passthroughs may not carry them).
- Top-level `session_id` on `CodeSessionContext` / `ExecuteResult` /
  artifact / response shapes is unchanged — that's the canonical
  execution-session name and a future engine that uses one session
  concept lands on it directly.

ToolNode propagates `kind` and `version?` through:
- `runTool` injection (`_injected_files`)
- `getCodeSessionContext` (event-driven path)
- `updateCodeSession` and `storeCodeSessionFromResults` (already
  pass-through via spread; new optional fields flow naturally)

Defaults:
- ToolNode and the `/files` fallback tag missing-kind files as
  `kind: 'user'`. Most ad-hoc files are user-private; shared
  resources (`skill`, `agent`) populate their kind upstream in LC.

Tests:
- 787 / 787 tools tests pass; tsc clean.
- 20 / 20 in `ToolNode.session.test.ts` (per-file storage and kind
  pinned at every boundary).

Coordination:
- Companion: LibreChat #12960 (sets `kind` at every write site,
  threads `skill.version` through skill primes), codeapi #1455
  (rewrites `resolveSessionKey` as a kind-switch with
  `authContext.tenantId` prefix). All three deploy in lockstep —
  pre-release, no aliases retained.
Tightens the per-file ref contract codeapi expects on `_injected_files`:

- `CodeEnvFile` is now a discriminated union on `kind`. `version` is
  statically required for `kind: 'skill'` and statically forbidden for
  `agent` / `user`. The constraint holds at compile time on every
  consumer, not just at codeapi's runtime validator boundary.
- `CODE_ENV_KINDS` const-tuple keeps the runtime list and the TS union
  locked together — adding a new kind to the tuple updates both at
  once, so a partial update won't silently widen one side.
- JSDoc on `id` calls out the per-kind asymmetry: skill UUID for
  `'skill'`, agent id for `'agent'`, informational-only for `'user'`
  (codeapi resolves user identity from the auth context). Future
  readers seeing `RequestFile { id, kind: 'user' }` shouldn't assume
  `id` routes anywhere.

ToolNode collapses two near-identical CodeEnvFile constructors
(the runTool-injection path and the event-driven `getCodeSessionContext`
path) onto a single `toInjectedFileRef` helper. The helper does the
discriminated-union narrowing in one place instead of duplicating the
"if kind===skill set version" logic at every call site, where the
previous shape — `const ref = { ... }; if (file.version != null) ref.version = file.version;` —
no longer typechecks against the union variants. Skill refs missing
`version` fall back to `'user'` so an upstream contract bug surfaces
as a degraded sessionKey rather than a runtime crash; primeSkillFiles
is the only writer and always sets `version`.

Companion: LibreChat #12960 (mirrors the discriminated union on
`CodeEnvRef`, drops `entity_id` everywhere). codeapi #1455 already
validates the same shape on the wire.
@danny-avila danny-avila force-pushed the storage-session-rename branch from e8d05eb to 7af210b Compare May 7, 2026 20:20
@danny-avila danny-avila changed the title 🧱 refactor: storage_session_id rename + kind discriminator (3.1.79-dev.0) 🧱 refactor: storage_session_id rename + kind discriminator (3.1.80-dev.0) May 7, 2026
@danny-avila danny-avila merged commit 7e48683 into dev May 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant